package com.ninth.element.generated.number.generate.mysql;

import com.ninth.element.generated.number.NumberStarterConstants;
import com.ninth.element.generated.number.generate.common.exception.ServiceException;
import com.ninth.element.generated.number.generate.common.pojo.enums.ResetTypeEnum;
import com.ninth.element.generated.number.generate.fallback.DefaultIdFallback;
import com.ninth.element.generated.number.generate.fallback.IdFallback;
import com.ninth.element.generated.number.property.MysqlWithNumberProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author LiYUJi
 **/
public class MysqlIdTemplate implements InitializingBean {

    /**
     * 区分开各系统使用的序号，作为前缀拼接 - 默认获取spring应用名
     */
    private String appName = "NUMBER_DEFAULT_APP";

    @Autowired
    private Environment environment;

    @Autowired
    private MysqlWithNumberProperty mysqlWithNumberProperty;

    @Autowired
    private DefaultIdFallback defaultIdFallback;

    @Autowired
    private MysqlIdWorker mysqlIdWorker;


    private static final Logger log = LoggerFactory.getLogger(MysqlIdTemplate.class);


    private MysqlIdPatternConfiguration mysqlIdPatternConfiguration;


    public MysqlIdTemplate() {
        this.mysqlIdPatternConfiguration = MysqlIdPatternConfiguration.builder().build();
    }

    public MysqlIdTemplate(MysqlIdPatternConfiguration mysqlIdPatternConfiguration) {
        this.mysqlIdPatternConfiguration = mysqlIdPatternConfiguration;
    }

    /**
     * 按规则生成ID
     *
     * @param key               mysql 作为ID前缀（若配置了需要前缀）, 同时还起到 数据库 serialNumType 字段的作用 用来区分类型
     * @param dateTimeFormatter 拼接日期格式，为空编号则不拼接日期，默认null
     * @param digits            mysql id位数，id不足位数则在前面补0，默认0
     * @param increment         mysql id增长步长，默认1
     * @param initial           mysql id初始化值，默认从0开始
     * @param needPrefix        是否需要前缀，默认false不拼接
     * @param isResetDaily      是否需要重置序列，默认false不重置，为true的话序列会根据重置类型重置为initial的参数值，如果参数类型没有指定则 默认为每天
     * @param resetType         重置类型 ResetTypeEnum
     * @param idFallback        mysql不可用时，服务降级的ID生成策略
     * @return ID
     */
    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Integer increment, Long initial, boolean needPrefix, boolean isResetDaily, ResetTypeEnum resetType, IdFallback idFallback) {
        if (resetType == ResetTypeEnum.MILLISECOND || resetType == ResetTypeEnum.SECOND) {
            throw new ServiceException("mysql不支持秒级和毫秒级");
        }
        Assert.hasLength(key, "key must not be null or empty!");
        StringBuilder seq = new StringBuilder();
        if (needPrefix && !StringUtils.isEmpty(key)) {
            seq.append(key);
        }
        try {
            // 如果开启了重置，而没有设置重置类型则 默认为每天
            if (isResetDaily && resetType == null) {
                resetType = ResetTypeEnum.DAY;
                log.warn("当前参数配置了每天重置序列(isResetDaily=true)，但未配置日期重置类型(resetType)，系统将为您配置默认的resetType参数为 DAY，建议您配置isResetDaily=true的同时配置resetType参数");
            }
            String dateStr = (dateTimeFormatter != null) ? dateTimeFormatter.format(LocalDateTime.now()) : "";
            Long id = mysqlIdWorker.incr(getsSerialNumType(key), isResetDaily, resetType, increment, initial);
            // 格式化 id
            String fmt = String.format("%%0%dd", digits);
            String idStr = String.format(fmt, id);
            String formatRedisId = digits > 0 ? idStr : String.valueOf(id);
            String result = seq.append(dateStr).append(formatRedisId).toString();
            log.info("已生成编号： {}", result);
            return result;
        } catch (Exception ex) {
            //为了容错和服务降级, 当Redis不能提供服务或其他异常错误导致生成失败时
            log.error("通过Redis生成唯一ID失败，采用服务降级策略生成", ex);
        }
        //没有配置ID服务降级策略，则使用默认策略
        idFallback = (idFallback == null) ? defaultIdFallback : idFallback;
        return seq.append(idFallback.nextId()).toString();
    }

    /**
     * 获取mysql存的 serialNumType ，-------这个值是由用户指定的serialNumType字符串加 appName 拼接而成
     *
     * @param key key
     * @return String
     */
    private String getsSerialNumType(String key) {
        StringBuilder serialNumTypeKey = new StringBuilder().append(appName.toUpperCase());
        serialNumTypeKey.append("_").append(key);
        return serialNumTypeKey.toString();
    }


    public String getAppName() {
        String applicationName = mysqlWithNumberProperty.getAppName();
        if (StringUtils.isEmpty(applicationName)) {
            if (environment != null && !StringUtils.isEmpty(environment.getProperty(NumberStarterConstants.SPRING_APPLICATION_NAME))) {
                applicationName = environment.getProperty(NumberStarterConstants.SPRING_APPLICATION_NAME);
            } else {
                applicationName = this.appName;
            }
        }
        return applicationName;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.appName = getAppName();
    }


    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Long initial, boolean needPrefix, boolean isResetDaily, ResetTypeEnum resetType) {
        return next(key, dateTimeFormatter, digits, mysqlIdPatternConfiguration.getIncrement(), initial, needPrefix, isResetDaily, resetType, mysqlIdPatternConfiguration.getIdFallback());
    }

    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Long initial, boolean needPrefix, boolean isResetDaily) {
        return next(key, dateTimeFormatter, digits, mysqlIdPatternConfiguration.getIncrement(), initial, needPrefix, isResetDaily, ResetTypeEnum.DAY, mysqlIdPatternConfiguration.getIdFallback());
    }

    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Long initial, boolean needPrefix) {
        return next(key, dateTimeFormatter, digits, initial, needPrefix, mysqlIdPatternConfiguration.isResetDaily());
    }

    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Long initial) {
        return next(key, dateTimeFormatter, digits, initial, mysqlIdPatternConfiguration.isNeedPrefix());
    }

    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits) {
        return next(key, dateTimeFormatter, digits, mysqlIdPatternConfiguration.getInitial());
    }

    public String next(String key, int digits) {
        return next(key, mysqlIdPatternConfiguration.getDateTimeFormatter(), digits);
    }

    public String next(String key, Long initial) {
        return next(key, mysqlIdPatternConfiguration.getDateTimeFormatter(), mysqlIdPatternConfiguration.getDigits(), initial);
    }

    public String next(String key) {
        return next(key, mysqlIdPatternConfiguration.getDigits());
    }

    public String next(Long initial) {
        return next(mysqlIdPatternConfiguration.getKey(), initial);
    }

    public String next() {
        return next(mysqlIdPatternConfiguration.getKey());
    }

}
